﻿/* Copyright (C) 2008 - 2011 by John Cronin
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

using System;
using System.Collections.Generic;
using System.Text;

namespace tysila
{
    partial class Assembler
    {
        TypeToCompile? tysos_type_ttc = null;
        Layout tysos_type_l = null;
        string tysos_type_ti = null;
        string tysos_type_vt = null;
        Dictionary<string, int> tysos_type_offsets = null;

        Layout GetTysosTypeLayout()
        {
            if(tysos_type_l == null)
            {
                tysos_type_ttc = Metadata.GetTTC("tysos", "tysos", "TysosType", this);
                tysos_type_l = Layout.GetLayout(tysos_type_ttc.Value, this);
                tysos_type_ti = Mangler.Mangle(tysos_type_ttc.Value, this) + "_TI";
                tysos_type_vt = Mangler.Mangle(tysos_type_ttc.Value, this) + "_VT";

                tysos_type_offsets = new Dictionary<string, int>();
                {
                    foreach (Layout.Field f in tysos_type_l.InstanceFields)
                    {
                        if (!tysos_type_offsets.ContainsKey(f.name))
                            tysos_type_offsets.Add(f.name, f.offset);
                    }
                }
            }

            return tysos_type_l;
        }

#if MT
        public void AssembleType(object ttcr) { AssembleType(ttcr as TTCRequest); }
        public void AssembleType(TTCRequest ttcr) { AssembleType(ttcr.ttc, ttcr.of); }
#endif
        public void AssembleType(Assembler.TypeToCompile ttc, IOutputFile of)
        {
            if (of == null)
            {
#if MT
                rtc--;
#endif
                return;
            }

            Layout l = Layout.GetLayout(ttc, this);

            lock (of)
            {
                /* Write the static fields */
                if (l.StaticFields.Count > 0)
                {
                    of.AlignData(GetSizeOfPointer());
                    of.AddDataSymbol(of.GetData().Count, l.static_object_name);
                    for (int i = 0; i < l.StaticClassSize; i++)
                        of.GetData().Add(0);
                }

                /* Write the vtables */
                of.AlignData(GetSizeOfPointer());
                int vtable_start = of.GetData().Count;
                of.AddDataSymbol(vtable_start, l.vtable_object_name);

                /* Add in any VTableAliases */
                foreach (Metadata.CustomAttributeRow car in ttc.type.CustomAttrs)
                {
                    Metadata.MethodDefRow camdr = Metadata.GetMethodDef(car.Type.ToToken(), this);

                    string caname = Mangler.Mangle(camdr, this);
                    caname = caname.Substring(Mangler.Mangle(camdr.m).Length);

                    if (caname == "_N3_ABI_T20_VTableAliasAttribute_Special_M7_#2Ector_S8_2001010E")
                    {
                        if (car.Value[0] != 0x01)
                            throw new NotSupportedException();
                        if (car.Value[1] != 0x00)
                            throw new NotSupportedException();
                        int offset = 2;
                        int len = Metadata.ReadCompressedInteger(car.Value, ref offset);
                        string s = new UTF8Encoding().GetString(car.Value, offset, len);
                        of.AddDataSymbol(vtable_start, s);
                    }
                }
                
                /* Add the typeinfo pointer */
                of.AddDataRelocation(of.GetData().Count, l.typeinfo_object_name, DataToDataRelocType(), 0);
                WriteZeroIntPtr(of);
                Requestor.RequestTypeInfo(ttc);

                /* Store the address of the itablemap pointer for later */
                int itablemap_pointer_offset = of.GetData().Count;
                WriteZeroIntPtr(of);

                /* Write out the virtual methods */
                foreach (Layout.Method vm in l.VirtualMethods)
                {
                    of.AddDataRelocation(of.GetData().Count, vm.implementation, DataToDataRelocType(), 0);
                    WriteZeroIntPtr(of);

                    if (vm.implementation_mtc.HasValue)
                        Requestor.RequestMethod(vm.implementation_mtc.Value);
                }

                /* Now point the itablemap pointer to here */
                of.AddDataRelocation(itablemap_pointer_offset, l.vtable_object_name, DataToDataRelocType(), of.GetData().Count - vtable_start);

                /* Write out the interfaces list */
                List<int> interface_methodlistptr_offsets = new List<int>();
                foreach (Layout.Interface iface in l.Interfaces)
                {
                    of.AddDataRelocation(of.GetData().Count, iface.typeinfo_name, DataToDataRelocType(), 0);
                    WriteZeroIntPtr(of);
                    interface_methodlistptr_offsets.Add(of.GetData().Count);
                    WriteZeroIntPtr(of);
                }

                /* Null terminate the list */
                WriteZeroIntPtr(of);

                /* Now write the actual interface method implementations */
                for (int idx = 0; idx < l.Interfaces.Count; idx++)
                {
                    /* First point the interface methodlist pointer to here */
                    of.AddDataRelocation(interface_methodlistptr_offsets[idx], l.vtable_object_name, DataToDataRelocType(), of.GetData().Count - vtable_start);

                    /* Now write out the methods */
                    foreach (Layout.Method im in l.Interfaces[idx].methods)
                    {
                        of.AddDataRelocation(of.GetData().Count, im.implementation, DataToDataRelocType(), 0);
                        WriteZeroIntPtr(of);

                        if (im.implementation_mtc.HasValue)
                            Requestor.RequestMethod(im.implementation_mtc.Value);
                    }
                }
            }
#if MT
            rtc--;
#endif
        }

        private void WriteZeroIntPtr(IOutputFile of)
        {
            int count = this.GetSizeOf(new Signature.Param(BaseType_Type.I));
            for (int i = 0; i < count; i++)
                of.GetData().Add(0);
        }

        private void WriteZeroInt32(IOutputFile of)
        {
            int count = this.GetSizeOf(new Signature.Param(BaseType_Type.I4));
            for (int i = 0; i < count; i++)
                of.GetData().Add(0);
        }

#if MT
        public void AssembleTypeInfo(object ttcr) { AssembleTypeInfo(ttcr as TTCRequest); }
        public void AssembleTypeInfo(Assembler.TTCRequest ttcr) { AssembleTypeInfo(ttcr.ttc, ttcr.of); }
#endif
        public void AssembleTypeInfo(Assembler.TypeToCompile ttc, IOutputFile of)
        {
            /* The TypeInfo structure is based on [tysos]tysos.TysosType which extends System.Type
             * 
             * Its fields are:
             * 
             * IntPtr                       __vtbl -> tysos.TysosType_VT
             * Int32                        __object_id
             * Int32                        __mutex_lock
             * System.RuntimeTypeHandle     _impl       // inherited from System.Type
             * TysosType                    Extends
             * string                       TypeName
             * string                       TypeNamespace
             * IntPtr                       Interfaces
             * IntPtr                       Fields
             * IntPtr                       Methods
             * IntPtr                       Events
             * IntPtr                       Properties
             * IntPtr                       NestedTypes
             * System.Reflection.Assembly   _Assembly
             * System.Relfection.Module     _Module
             * IntPtr                       Signature
             * IntPtr                       Sig_references
             * Int32                        Flags
             * Int32                        ImplFlags
             * Int32                        ClassSize
             * 
             * Interfaces, Fields, Methods, Events, Properties, NestedTypes and Sig_references
             * are pointers to 'simple arrays'.  These are null terminated lists of TysosType
             * references for composing into an actual array by the runtime.  If they are empty,
             * then the pointer itself can actually be null (rather than pointing to a null array
             * which would be wasteful).
             * 
             * Signature is a pointer to a null-terminated byte stream.  It contains similar entries
             * to the signatures in metadata, however ComplexTypes (those represented by
             * ELEMENT_TYPE_CLASS and ELEMENT_TYPE_VALUETYPE) are followed by a single byte which is
             * an index into the Sig_references array.  Note this limits each signature to reference
             * at most 256 discrete ComplexTypes (there are no duplicates within the Sig_references
             * array).  This should be enough for most purposes.
             * 
             * Flags is a copy of the Flags field from Metadata.
             * ImplFlags is used by the runtime for various reasons.  Currently the following bit
             * fields are defined:
             * 
             * 0x1          -   set to indicate that this class' static constructor (.cctor) has been called
             */

            GetTysosTypeLayout();

            if (of == null)
            {
#if MT
                rtc--;
#endif
                return;
            }

            Layout l = Layout.GetLayout(ttc, this);

            lock (of)
            {
                of.AlignData(GetSizeOfPointer());
                IList<byte> data_sect = of.GetData();
                int ti_top = data_sect.Count;

                of.AddDataSymbol(ti_top, l.typeinfo_object_name);

                // Create the type info struct in memory
                byte[] ti = new byte[tysos_type_l.ClassSize];

                // __vtbl pointer
                //of.AddDataRelocation(of.GetData().Count, tysos_type_vt, DataToDataRelocType(), 0);
                of.AddDataRelocation(ti_top + tysos_type_offsets["IntPtr __vtbl"], "__tysos_type_vt", DataToDataRelocType(), 0);

                // __obj_id
                SetByteArray(ti, tysos_type_offsets["Int32 __object_id"], ToByteArraySignExtend(next_object_id.Increment, 4));

                // System.RuntimeTypeHandle _impl
                // TODO

                // tysos.TysosType Extends
                if (ttc.type.Extends.Value != null)
                {
                    Assembler.TypeToCompile extends_ttc = Metadata.GetTTC(ttc.type.Extends, ttc, this);
                    Requestor.RequestTypeInfo(extends_ttc);
                    of.AddDataRelocation(ti_top + tysos_type_offsets["tysos.TysosType Extends"], Mangler.Mangle(extends_ttc, this) + "_TI", DataToDataRelocType(), 0);
                }

                // string TypeName
                var v_n = ttc.type.m.StringTable.GetStringAddress(ttc.type.TypeName, this);
                of.AddDataRelocation(ti_top + tysos_type_offsets["String TypeName"], v_n.base_var.v.label, DataToDataRelocType(), v_n.constant_offset);

                // string TypeNamespace
                var v_ns = ttc.type.m.StringTable.GetStringAddress(ttc.type.TypeNamespace, this);
                of.AddDataRelocation(ti_top + tysos_type_offsets["String TypeNamespace"], v_ns.base_var.v.label, DataToDataRelocType(), v_ns.constant_offset);

                // System.Reflection.Assembly _Assembly
                // TODO

                // System.Reflection.Module _Module
                // TODO

                // Int32 Flags
                SetByteArray(ti, tysos_type_offsets["Int32 Flags"], ToByteArrayZeroExtend(ttc.type.Flags, 4));

                // Int32 ImplFlags
                SetByteArray(ti, tysos_type_offsets["Int32 ImplFlags"], ToByteArrayZeroExtend(0, 4));

                // Int32 ClassSize
                SetByteArray(ti, tysos_type_offsets["Int32 ClassSize"], ToByteArrayZeroExtend(l.ClassSize, 4));


                // Write the typeinfo structure to the output file
                append_byte_array(of.GetData(), ti);

                // Write signature
                of.AlignData(GetSizeOfPointer());
                int signature_top = data_sect.Count;
                Dictionary<Metadata.TypeDefRow, byte> typedef_table = new Dictionary<Metadata.TypeDefRow, byte>();
                byte next_typedef_id = 0;
                write_ti_signature(data_sect, ttc, ttc.tsig.Type, typedef_table, ref next_typedef_id);
                of.AddDataRelocation(ti_top + tysos_type_offsets["IntPtr Signature"], l.typeinfo_object_name, DataToDataRelocType(), signature_top - ti_top);

                // Write Sig_references
                of.AlignData(GetSizeOfPointer());
                int sigreferences_top = data_sect.Count;
                foreach (KeyValuePair<Metadata.TypeDefRow, byte> typedef_table_item in typedef_table)
                {
                    Assembler.TypeToCompile ref_ttc = Metadata.GetTTC(new Metadata.TableIndex(typedef_table_item.Key), ttc, this);
                    string ref_mangled_name = Mangler.Mangle(ref_ttc, this);
                    Requestor.RequestTypeInfo(ref_ttc);

                    of.AddDataRelocation(data_sect.Count, ref_mangled_name + "_TI", DataToDataRelocType(), 0);
                    WriteZeroIntPtr(of);
                }
                WriteZeroIntPtr(of);        // null terminate
                of.AddDataRelocation(ti_top + tysos_type_offsets["IntPtr Sig_references"], l.typeinfo_object_name, DataToDataRelocType(), sigreferences_top - ti_top);

                // Write interface table
                of.AlignData(GetSizeOfPointer());
                int interfaces_top = data_sect.Count;
                foreach (Layout.Interface iface in l.Interfaces)
                {
                    of.AddDataRelocation(of.GetData().Count, iface.typeinfo_name, DataToDataRelocType(), 0);
                    WriteZeroIntPtr(of);

                    Requestor.RequestTypeInfo(iface.iface);
                }
                WriteZeroIntPtr(of);
                of.AddDataRelocation(ti_top + tysos_type_offsets["IntPtr Interfaces"], l.typeinfo_object_name, DataToDataRelocType(), interfaces_top - ti_top);

                // Write Fields table
                of.AlignData(GetSizeOfPointer());
                int fields_top = data_sect.Count;
                foreach (Layout.Field f in l.InstanceFields)
                {
                    if (!f.field.field.RuntimeInternal && (Metadata.GetOwningType(f.field.field.m, f.field.field) == ttc.type))
                    {
                        of.AddDataRelocation(data_sect.Count, Mangler.Mangle(f.field.DefinedIn, this) + Mangler.Mangle(f.field, this) + "_FI", DataToDataRelocType(), 0);
                        WriteZeroIntPtr(of);
                        Requestor.RequestFieldInfo(f.field);
                    }
                }
                WriteZeroIntPtr(of);        // null terminate
                of.AddDataRelocation(ti_top + tysos_type_offsets["IntPtr Fields"], l.typeinfo_object_name, DataToDataRelocType(), fields_top - ti_top);

                // Write Methods table
                // TODO

                // Write Events table
                // TODO

                // Write Properties table
                // TODO

                // Write NestedTypes table
                // TODO
            }

#if MT
            rtc--;
#endif
        }

        private void write_ti_signature(IList<byte> sig, TypeToCompile containing_type, Signature.BaseOrComplexType cur_bct, Dictionary<Metadata.TypeDefRow, byte> typedef_table, ref byte next_typedef_id)
        {
            if (cur_bct is Signature.BaseType)
                sig.Add((byte)((Signature.BaseType)cur_bct).Type);
            else if (cur_bct is Signature.ComplexType)
            {
                Metadata.TypeDefRow tdr = Metadata.GetTypeDef(((Signature.ComplexType)cur_bct).Type, this);
                if (tdr.IsValueType(this))
                    sig.Add((byte)BaseType_Type.ValueType);
                else
                    sig.Add((byte)BaseType_Type.Class);
                if (!typedef_table.ContainsKey(tdr))
                    typedef_table.Add(tdr, next_typedef_id++);
                sig.Add(typedef_table[tdr]);
            }
            else if (cur_bct is Signature.BoxedType)
            {
                sig.Add((byte)BaseType_Type.Boxed);
                write_ti_signature(sig, containing_type, ((Signature.BoxedType)cur_bct).Type, typedef_table, ref next_typedef_id);
            }
            else if (cur_bct is Signature.GenericType)
            {
                sig.Add((byte)BaseType_Type.GenericInst);
                write_ti_signature(sig, containing_type, ((Signature.GenericType)cur_bct).GenType, typedef_table, ref next_typedef_id);
                byte[] comp_argcount = Metadata.CompressInteger(Convert.ToUInt32(((Signature.GenericType)cur_bct).GenParams.Count));
                foreach (byte b in comp_argcount)
                    sig.Add(b);
                foreach (Signature.BaseOrComplexType gp in ((Signature.GenericType)cur_bct).GenParams)
                    write_ti_signature(sig, containing_type, gp, typedef_table, ref next_typedef_id);
            }
            else if (cur_bct is Signature.ZeroBasedArray)
            {
                sig.Add((byte)BaseType_Type.SzArray);
                write_ti_signature(sig, containing_type, ((Signature.ZeroBasedArray)cur_bct).ElemType, typedef_table, ref next_typedef_id);
            }
            else if (cur_bct is Signature.ComplexArray)
            {
                Signature.ComplexArray ca = cur_bct as Signature.ComplexArray;
                sig.Add((byte)BaseType_Type.Array);
                write_ti_signature(sig, containing_type, ca.ElemType, typedef_table, ref next_typedef_id);
                append_byte_array(sig, Metadata.CompressInteger((uint)ca.Rank));
                append_byte_array(sig, Metadata.CompressInteger((uint)ca.Sizes.Length));
                foreach (int size in ca.Sizes)
                    append_byte_array(sig, Metadata.CompressInteger((uint)size));
                append_byte_array(sig, Metadata.CompressInteger((uint)ca.LoBounds.Length));
                foreach (int lobound in ca.LoBounds)
                    append_byte_array(sig, Metadata.CompressInteger(FromByteArrayU4(ToByteArray(lobound))));
            }
            else if (cur_bct is Signature.ManagedPointer)
            {
                sig.Add((byte)BaseType_Type.Byref);
                write_ti_signature(sig, containing_type, ((Signature.ManagedPointer)cur_bct).ElemType, typedef_table, ref next_typedef_id);
            }
            else if (cur_bct is Signature.UnmanagedPointer)
            {
                sig.Add((byte)BaseType_Type.Ptr);
                write_ti_signature(sig, containing_type, ((Signature.UnmanagedPointer)cur_bct).BaseType, typedef_table, ref next_typedef_id);
            }
            else
                throw new NotImplementedException(Signature.GetString(cur_bct, this));
        }

        private void append_byte_array(IList<byte> dest, byte[] src)
        {
            foreach (byte b in src)
                dest.Add(b);
        }
    }
}
